/*
 * Copyright (c) 2010 Vadim Zhukov <persgray@gmail.com>
 *
 * Permission to use, copy, modify, and distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice appear in all copies.
 *
 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 */


#include "common.h"

#include <sys/socket.h>
#include <sys/un.h>
#include <netinet/in.h>
#include <arpa/nameser.h>
#include <arpa/inet.h>

#include <assert.h>
#include <err.h>
#include <errno.h>
#include <fcntl.h>
#include <netdb.h>
#include <poll.h>
#include <signal.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

#include "peertask.h"
#include "peer.h"
#include "resolver.h"
#include "timestamp.h"


extern char			*__progname;
int				 debug = 0;
static volatile sig_atomic_t	 quit_requested = 0;


static void	usage(const char *msg);
static void	sighandler(int);
static int	new_collector_conn(int, struct peer *);

void
usage(const char *msg) {
	if (msg != NULL)
		fprintf(stderr, "%s: %s", __progname, msg);
	fprintf(stderr,
"usage: %s [-d] [-b addr]\n"
"  -b listenaddr  Listen on given address instead of system selected one.\n"
"                 You can also specify port by appending :number to listenaddr.\n"
"  -d             Do not daemonize and increase verbosity.\n",
	    __progname);
	exit(EXIT_FAILURE);
}

void
sighandler(int sig) {
	quit_requested = 1;
}

/*
 * Stub: just drop all messages now.
 */
void
handle_collector(struct peer *peer) {
	struct	peer_task *task;

	if (peer->pollptr->revents & (POLLOUT|POLLIN)) {
		peer_run_queues(peer);
		/* Just free the tasks resources */
		if (peer_has_completed_tasks(peer)) {
			while ((task = peer_pick_done_task(peer,
			    PEER_QUEUE_OUTPUT)))
				peer_free_task(task, 1);
			while ((task = peer_pick_done_task(peer,
			    PEER_QUEUE_INPUT)))
				peer_free_task(task, 1);
		}
		peer->pollptr->revents &= ~(POLLOUT|POLLIN);
	}
	if (peer->pollptr->revents)
		/* failed */
		(void)peer_close(peer);
}

int
new_collector_conn(int sock, struct peer *peer) {
	if (peer->conn.fd != -1) {
		DPRINTF(0, "collector was already connected, dropping previous"
		    "connection");
		(void)peer_close(peer);
	}
	return peer_accept(peer, sock);
}

int
main(int argc, char **argv) {
	struct	 peer *collector;
	struct	 bindaddr_list boundto;
	struct	 bindaddr *ba;
	struct	 pollfd *polls;
	struct	 sigaction siga;
	size_t	 nboundaddrs = 0, i;
	int	 c, n;

	collector = peer_new(0, NULL);
	if (collector == NULL)
		fatal("peer_new");
	SLIST_INIT(&boundto);

	while ((c = getopt(argc, argv, "b:df:p:t:")) != -1) {
		switch (c) {
		case 'b':
			ba = malloc(sizeof(struct bindaddr));
			if (ba == NULL)
				fatalx("malloc");
			bzero(ba, sizeof(struct bindaddr));
			if (fill_sa_from_user(optarg, ba,
			    SASPEC_HOST_NUMERIC | SASPEC_FOR_BINDING,
			    PEER_PORT) == -1)
				fatal("setting up listen address %s", optarg);
			SLIST_INSERT_HEAD(&boundto, ba, entry);
			nboundaddrs++;
			break;

		case 'd':
			debug++;
			break;

		default:
			usage(NULL);
		}
	}
	argc -= optind;
	argv += optind;
	if (argc > 0)
		usage("extra parameters in command line");

	/* set up listening socket(-s) */
	if (SLIST_EMPTY(&boundto)) {
		/*
		 * Do not use calloc(2, ) because we free bindaddr structures
		 * on cleanup one-by-one.
		 */
		ba = calloc(1, sizeof(struct bindaddr));
		if (ba == NULL)
			fatalx("calloc");
		fill_sa_from_user("0.0.0.0", ba,
		    SASPEC_HOST_NUMERIC | SASPEC_FOR_BINDING, FILTER_PORT);
		SLIST_INSERT_HEAD(&boundto, ba, entry);

		ba = calloc(1, sizeof(struct bindaddr));
		if (ba == NULL)
			fatalx("calloc");
		fill_sa_from_user("::", ba,
		    SASPEC_HOST_NUMERIC | SASPEC_FOR_BINDING, FILTER_PORT);
		SLIST_INSERT_HEAD(&boundto, ba, entry);

		nboundaddrs = 2;
	}
	polls = calloc(nboundaddrs + 1, sizeof(struct pollfd));
	if (polls == NULL)
		fatalx("calloc");
	n = 0;
	polls[n].fd = -1;    /* collector connection, only one */
	polls[n].events = POLLIN|POLLOUT;
	SLIST_FOREACH(ba, &boundto, entry) {
		ba->fd = socket(ba->ss.ss_family, SOCK_STREAM, 0);
		if (ba->fd == -1)
			fatal("(listening) socket");
		if (fcntl(ba->fd, F_SETFL, O_NONBLOCK) == -1)
			fatal("fcntl");
		if (bind(ba->fd, (struct sockaddr *)&ba->ss, sizeof(ba->ss)))
			fatal("bind");
		if (listen(ba->fd, 20))
			fatal("listen");
		polls[n].fd = ba->fd;
		polls[n].events = POLLIN;
	}
	while (!SLIST_EMPTY(&boundto)) {
		ba = SLIST_FIRST(&boundto);
		SLIST_REMOVE_HEAD(&boundto, entry);
		free(ba);
	}

	bzero(&siga, sizeof(struct sigaction));
	siga.sa_handler = sighandler;
	sigaction(SIGINT, &siga, NULL);
	sigaction(SIGTERM, &siga, NULL);
	sigaction(SIGUSR1, &siga, NULL);
	sigaction(SIGUSR2, &siga, NULL);
	siga.sa_handler = SIG_IGN;
	sigaction(SIGHUP, &siga, NULL);    /* XXX: re-read certificates? */

	if (!debug && daemon(0, 0))
		DPRINTF(0, "could not go daemon");

	for (;;) {
		n = poll(polls, nboundaddrs, INFTIM);
		switch (n) {
		case -1:
			if (errno == EINTR && quit_requested)
				goto cleanup;
			else
				DPRINTF(0, "poll");
			break;

		case 0:
			break;

		default:
			if (polls[0].fd != -1 && polls[0].revents) {
				n--;
				handle_collector(collector);
				if (peer_is_closed(collector))
					polls[0].fd = -1;
			}
			for (i = 1; n > 0; i++) {
				if (polls[i].fd != -1 && polls[i].revents) {
					n--;
					if (polls[i].revents &
					    (POLLHUP|POLLERR)) {
						DPRINTF(0,
						    "listening fd %d failed",
						    polls[i].fd);
						(void)close(polls[i].fd);
						polls[i].fd = -1;
					} else if (polls[i].revents & POLLIN) {
						new_collector_conn(polls[i].fd,
						    collector);
					}
				}
			}
		}

		if (quit_requested)
			break;
	}

cleanup:
	for (i = 0; i < nboundaddrs; i++)
		(void)close(polls[i].fd);
	peer_free(collector);
	return (1);
}
